using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Xml;
using System.IO.Compression;
using System.IO;

namespace EuropeEngulfed.NET
{
    class TCPClient
    {
        private delegate void EEMainFormQueueMessagesCallback(EuropeEngulfedNETForm form, List<string> listMessages);
        public static string UNKNOWN_GAME_DATE = "UnknownGameDate";
        private Socket mainSocket = null;
        private Socket infoSocket = null;
        private IPEndPoint ipe;
        private IPEndPoint infoipe;
        private byte[] requestbuffer;
        private byte[] replybuffer;
        private byte[] replybufferDecompressed;
        private byte[] requestInfobuffer;
        private byte[] replyInfobuffer;
        private bool doNotPollServer = false;
        private bool waitingToReceiveInfo = false;
        private int bytesReceivedSendClientMessage = 0;
        public enum ClientMessageType
        {
            OK,
            DisplayWeather,
            DisplayCalculatingSupply,
            AskForDefenderOptions,
            DoDefenderCombat,
            AskDefenderToAssignLosses,
            AskAttackerToAssignLosses,
            AskDefenderForCounterAttack,
            DoStrategicBombingWarfare,
            ReportStrategicBombingWarfare,
            DoStrategicUBoatWarfare,
            ReportStrategicUBoatWarfare,
            DoStrategicVWeaponWarfare,
            ReportStrategicVWeaponWarfare,
            DoAxisProductionPhase,
            DoAlliedProductionPhase,
            AskForStrategicWarfareInterception,
            DoSupplySelection,
            DoInitialOps,
            DoBreakThroughOps,
            DoInitialStrategicMovement,
            DoNewCountryDeployment,
            AskForNavalInterception,
            ReportForNavalInterception,
            DoBreakThroughCombat,
            DoInitialCombatPhase,
            AskForGSUCombatCasualties,
            AskDefenderForRetreatBeforeCombatMoves,
            AskForBreakThroughOption,
            DoBreakThroughStrategicMovement,
            InformPlayerOfConquest,
            DoFleetPointTransfers,
            DoFortressOverStackMoves,
            AskAxisPlayerForVichy,
            InformPlayer,
            DoAxisRedeploymentOfSovietUpFrontDefenses,
            InformPlayerOfVictory,
            AssignLossesReturn,
            boolReturn,
            DeploymentMovesReturn,
            AskForDefenderOptionsReturn,
            AskDefenderForRetreatBeforeCombatMovesReturn,
            AskForGSUCombatCasualtiesReturn,
            AskDefenderForCounterAttackReturn,
            InformPlayerOfChangeOfPhase,
        };

        public TCPClient(IPAddress addressToConnectTo)
        {
            ipe = new IPEndPoint(addressToConnectTo, TCPServer.EENET_Port);
            infoipe = new IPEndPoint(addressToConnectTo, TCPServer.EENET_InformationPort);
            mainSocket = new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            infoSocket = new Socket(infoipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            requestbuffer = new byte[204800];
            replybuffer = new byte[204800];
            replybufferDecompressed = new byte[204800];
            requestInfobuffer = new byte[500];
            replyInfobuffer = new byte[500];
        }

        public bool Connect()
        {
            try
            {
                mainSocket.Connect(ipe);
                infoSocket.Connect(infoipe);
            }
            catch (SocketException e)
            {
                System.Console.Write(e.ToString());
            }

            return mainSocket.Connected;
        }

        private ClientMessageType GetClientMessageFromHeader(int bytesReceived, ref int numBytesFirstHeader)
        {
            ClientMessageType clientMessageReceived = ClientMessageType.OK;
            if (bytesReceived > 0)
            {
                numBytesFirstHeader = 0;
                while (replybufferDecompressed[numBytesFirstHeader] != 0)
                {
                    numBytesFirstHeader++;
                    if (numBytesFirstHeader >= replybufferDecompressed.Length)
                        break;
                }

                string serverMessageString = Encoding.ASCII.GetString(replybufferDecompressed, 0, numBytesFirstHeader);
                numBytesFirstHeader++;
                try
                {
                    return (ClientMessageType)Enum.Parse(typeof(ClientMessageType), serverMessageString);
                }
                catch
                {
                    return ClientMessageType.OK;
                }
            }
            return clientMessageReceived;
        }

        private int ReadAllBytesFromStream(Stream stream, byte[] buffer)
        {
            // Use this method is used to read all bytes from a stream.
            int offset = 0;
            int totalCount = 0;
            while (true)
            {
                int bytesRead = stream.Read(buffer, offset, 100);
                if (bytesRead == 0)
                {
                    break;
                }
                offset += bytesRead;
                totalCount += bytesRead;
            }
            return totalCount;
        }

        public GameState GetLatestGameState(string requestedGameDateForMoveHistory)
        {
            while (true)
            {
                string requestString = Enum.GetName(typeof(TCPServer.ServerRequestMessage), TCPServer.ServerRequestMessage.GetCurrentGameState);
                requestbuffer.Initialize();
                Encoding.ASCII.GetBytes(requestString, 0, requestString.Length, requestbuffer, 0);
                Encoding.ASCII.GetBytes(requestedGameDateForMoveHistory, 0, requestedGameDateForMoveHistory.Length, requestbuffer, requestString.Length + 1);

                mainSocket.Send(requestbuffer, requestedGameDateForMoveHistory.Length + requestString.Length + 1, SocketFlags.None);
                int bytesReceived = mainSocket.Receive(replybuffer, 0, (int)replybuffer.Length, SocketFlags.None); ;
                while (bytesReceived <= 0 || ((char)replybuffer[bytesReceived - 1]) != TCPServer.EndOfMessage)
                {
                    bytesReceived += mainSocket.Receive(replybuffer, bytesReceived, (int)replybuffer.Length - bytesReceived, SocketFlags.None);
                }
                System.IO.MemoryStream ms = new System.IO.MemoryStream(replybuffer, 0, bytesReceived - 1);
                GZipStream zipStream = new GZipStream(ms, CompressionMode.Decompress);
                replybufferDecompressed.Initialize();
                int unCompressedBytesReceived = ReadAllBytesFromStream(zipStream, replybufferDecompressed);

                int numBytesFirstHeader = 0;
                if (GetClientMessageFromHeader(unCompressedBytesReceived, ref numBytesFirstHeader) == ClientMessageType.OK)
                {
                    string XmlString = Encoding.ASCII.GetString(replybufferDecompressed, numBytesFirstHeader, unCompressedBytesReceived - (numBytesFirstHeader));
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.Load(XmlReader.Create(new System.IO.StringReader(XmlString)));
                    return GameState.Deserialise(xmlDoc);
                }
            }
        }

        private void ClearBuffers()
        {
            for (int ibyte = 0; ibyte < requestbuffer.Length; ibyte++)
            {
                requestbuffer[ibyte] = 0;
            }

            for (int ibyte = 0; ibyte < replybufferDecompressed.Length; ibyte++)
            {
                replybufferDecompressed[ibyte] = 0;
            }
        }

        private void ClearInfoRequestBuffers()
        {
            for (int ibyte = 0; ibyte < requestInfobuffer.Length; ibyte++)
            {
                requestInfobuffer[ibyte] = 0;
            }
        }

        private void ClearReplyInfoBuffer()
        {
            for (int ibyte = 0; ibyte < replyInfobuffer.Length; ibyte++)
            {
                replyInfobuffer[ibyte] = 0;
            }
        }

        private void EndReceiveSendClientMessage(IAsyncResult ar)
        {
            bytesReceivedSendClientMessage = mainSocket.EndReceive(ar);
        }

        private string GetWindowTitleStringAccordingToClientMessage(TCPServer.ServerRequestMessage expectedServerMessage)
        {
            switch(expectedServerMessage)
            {
                case TCPServer.ServerRequestMessage.AskAttackerToAssignLosses:
                    return "Waiting for Attacker to assign losses";
                case TCPServer.ServerRequestMessage.AskDefenderForCounterAttack:
                    return "Waiting for Defender CounterAttack Options";
                case TCPServer.ServerRequestMessage.AskDefenderForRetreatBeforeCombatMoves:
                    return "Waiting for Defender Retreat Before Combat Moves";
                case TCPServer.ServerRequestMessage.AssignLossesReturn:
                case TCPServer.ServerRequestMessage.AskDefenderToAssignLosses:
                    return "Waiting for Player to Assign Losses";
                case TCPServer.ServerRequestMessage.AskForGSUCombatCasualties:
                case TCPServer.ServerRequestMessage.AskForNavalInterception:
                    return "Waiting for Naval Interception Decision";
                case TCPServer.ServerRequestMessage.AskForNewCountryDeployment:
                    return "Waiting for New Country Deployment";
                case TCPServer.ServerRequestMessage.DetermineStrategicWarInterception:
                    return "Waiting for Strategic Warfare Interception";
                case TCPServer.ServerRequestMessage.DoDefenderBreakThroughCombat:
                    return "Waiting For Defender Breakthrough Combat";
                case TCPServer.ServerRequestMessage.DoDefenderCombat:
                    return "Waiting For Defender Combat Options";
                default:
                    return "Waiting For Remote Player";
            }
        }

        public ServerMessage SendClientMessage(ClientMessage clientMessage, ClientMessageType expectedServerMessage, IPlayer player)
        {
            string messageParams;
            int numBytesFirstHeader = 0;
            int unCompressedBytesReceived;
            System.Xml.XmlDocument xmlDoc;

            ClientMessageType clientMessageTypeReturned = ClientMessageType.AskAttackerToAssignLosses;
            XmlDocument xmlParams = new XmlDocument();
            xmlParams.AppendChild(clientMessage.Serialise(xmlParams));
            string requestString = Enum.GetName(typeof(TCPServer.ServerRequestMessage), clientMessage.ServerMessageToReturn);
            ClearBuffers();
            Encoding.ASCII.GetBytes(requestString, 0, requestString.Length, requestbuffer, 0);

            System.IO.StringWriter sw = new System.IO.StringWriter();
            XmlTextWriter xmlWriter = new XmlTextWriter(sw);
            xmlWriter.Formatting = Formatting.None;
            xmlParams.WriteTo(xmlWriter);
            Encoding.ASCII.GetBytes(sw.ToString(), 0, sw.ToString().Length, requestbuffer, requestString.Length + 1);

            mainSocket.Send(requestbuffer, requestString.Length + 1 + sw.ToString().Length, SocketFlags.None);
            doNotPollServer = true;
            do
            {
                string previousWindowTitle = EuropeEngulfedNETForm.MainForm.ToolStripText;
                IAsyncResult asynchResult = mainSocket.BeginReceive(replybuffer, 0, replybuffer.Length, SocketFlags.None, new AsyncCallback(EndReceiveSendClientMessage), null);
                doNotPollServer = true;
                EuropeEngulfedNETForm.MainForm.ToolStripText = previousWindowTitle + " - " + GetWindowTitleStringAccordingToClientMessage(clientMessage.ServerMessageToReturn);
                EuropeEngulfedNETForm.MainForm.ToggleUIMode(true);
                while(!asynchResult.IsCompleted)
                {
                    System.Threading.Thread.Sleep(50);
                    System.Windows.Forms.Application.DoEvents();
                }
                EuropeEngulfedNETForm.MainForm.ToggleUIMode(false);
                EuropeEngulfedNETForm.MainForm.ToolStripText = previousWindowTitle;
                doNotPollServer = false;

                System.IO.MemoryStream ms = new System.IO.MemoryStream(replybuffer, 0, bytesReceivedSendClientMessage - 1);
                GZipStream zipStream = new GZipStream(ms, CompressionMode.Decompress);
                replybufferDecompressed.Initialize();
                unCompressedBytesReceived = ReadAllBytesFromStream(zipStream, replybufferDecompressed);
                numBytesFirstHeader = 0;
                clientMessageTypeReturned = GetClientMessageFromHeader(unCompressedBytesReceived, ref numBytesFirstHeader);
                if (clientMessageTypeReturned != expectedServerMessage)
                {
                    if (clientMessageTypeReturned != ClientMessageType.OK)
                    {
                        ClientMessage clientMessageToReturn = null;
                        xmlDoc = new System.Xml.XmlDocument();
                        messageParams = Encoding.ASCII.GetString(replybufferDecompressed, numBytesFirstHeader, unCompressedBytesReceived - (numBytesFirstHeader));
                        xmlDoc.Load(System.Xml.XmlReader.Create(new System.IO.StringReader(messageParams)));
                        if ((clientMessageToReturn = ((ServerMessagePlayerCall)ServerMessage.Deserialise(xmlDoc.FirstChild)).CallPlayer(player)) != null)
                            this.ReturnClientMessage(clientMessageToReturn);
                    }
                }
            }
            while (clientMessageTypeReturned != expectedServerMessage);

            messageParams = Encoding.ASCII.GetString(replybufferDecompressed, numBytesFirstHeader, unCompressedBytesReceived - (numBytesFirstHeader));
            xmlDoc = new System.Xml.XmlDocument();
            xmlDoc.Load(System.Xml.XmlReader.Create(new System.IO.StringReader(messageParams)));
            doNotPollServer = false;
            return ServerMessage.Deserialise(xmlDoc.FirstChild);
        }

        public void ReturnClientMessage(ClientMessage clientMessage)
        {
            int numBytesFirstHeader = 0;
            int unCompressedBytesReceived;
            do
            {
                XmlDocument xmlParams = new XmlDocument();
                xmlParams.AppendChild(clientMessage.Serialise(xmlParams));
                string requestString = Enum.GetName(typeof(TCPServer.ServerRequestMessage), clientMessage.ServerMessageToReturn);
                ClearBuffers();
                Encoding.ASCII.GetBytes(requestString, 0, requestString.Length, requestbuffer, 0);

                System.IO.StringWriter sw = new System.IO.StringWriter();
                XmlTextWriter xmlWriter = new XmlTextWriter(sw);
                xmlWriter.Formatting = Formatting.None;
                xmlParams.WriteTo(xmlWriter);
                Encoding.ASCII.GetBytes(sw.ToString(), 0, sw.ToString().Length, requestbuffer, requestString.Length + 1);

                mainSocket.Send(requestbuffer, requestString.Length + 1 + sw.ToString().Length, SocketFlags.None);

                int bytesReceived = mainSocket.Receive(replybuffer, 0, (int)replybuffer.Length, SocketFlags.None);
                while (bytesReceived <= 0 || ((char)replybuffer[bytesReceived - 1]) != TCPServer.EndOfMessage)
                {
                    bytesReceived += mainSocket.Receive(replybuffer, bytesReceived, (int)replybuffer.Length - bytesReceived, SocketFlags.None);
                }
                System.IO.MemoryStream ms = new System.IO.MemoryStream(replybuffer, 0, bytesReceived - 1);
                GZipStream zipStream = new GZipStream(ms, CompressionMode.Decompress);
                replybufferDecompressed.Initialize();
                unCompressedBytesReceived = ReadAllBytesFromStream(zipStream, replybufferDecompressed);
                numBytesFirstHeader = 0;
            }
            while (GetClientMessageFromHeader(unCompressedBytesReceived, ref numBytesFirstHeader) != ClientMessageType.OK);
        }

        public ClientMessageType PollTCPServer(ref string messageParams)
        {
            try
            {
                try
                {
                    if (!waitingToReceiveInfo)
                    {
                        waitingToReceiveInfo = true;
                        string requestInfoString = Enum.GetName(typeof(TCPServer.ServerRequestMessage), TCPServer.ServerRequestMessage.Polling);
                        ClearInfoRequestBuffers();
                        Encoding.ASCII.GetBytes(requestInfoString, 0, requestInfoString.Length, requestInfobuffer, 0);
                        List<string> messagesToSend = EuropeEngulfedNETForm.MainForm.GetInfoMessagesToSend();
                        int position = requestInfoString.Length + 1;
                        foreach(string message in messagesToSend)
                        {
                            if ((position + message.Length) > requestInfobuffer.Length)
                                break;
                            Encoding.ASCII.GetBytes(message, 0, message.Length, requestInfobuffer, position);
                            position += message.Length + 1;
                        }
                        infoSocket.BeginSend(requestInfobuffer, 0, position, SocketFlags.None, new AsyncCallback(EndSendInformation), null);
                    }
                }
                catch (SocketException e)
                {
                    waitingToReceiveInfo = true;
                    infoSocket.Disconnect(true);
                    infoSocket.BeginConnect(infoipe, new AsyncCallback(EndConnectInformation), null);
                    System.Console.WriteLine("Problem with information socket" + e.ToString());
                }

                if (doNotPollServer)
                    return ClientMessageType.OK;
                else
                {
                    ClientMessageType clientMessageToReturn = ClientMessageType.OK;
                    string requestString = Enum.GetName(typeof(TCPServer.ServerRequestMessage), TCPServer.ServerRequestMessage.Polling);
                    ClearBuffers();
                    Encoding.ASCII.GetBytes(requestString, 0, requestString.Length, requestbuffer, 0);
                    mainSocket.Send(requestbuffer, requestString.Length + 1, SocketFlags.None);
                    int bytesReceived = mainSocket.Receive(replybuffer, 0, (int)replybuffer.Length, SocketFlags.None); ;
                    while (bytesReceived <= 0 || ((char)replybuffer[bytesReceived - 1]) != TCPServer.EndOfMessage)
                    {
                        bytesReceived += mainSocket.Receive(replybuffer, bytesReceived, (int)replybuffer.Length - bytesReceived, SocketFlags.None);
                    }
                    System.IO.MemoryStream ms = new System.IO.MemoryStream(replybuffer, 0, bytesReceived - 1);
                    GZipStream zipStream = new GZipStream(ms, CompressionMode.Decompress);
                    replybufferDecompressed.Initialize();
                    int unCompressedBytesReceived = ReadAllBytesFromStream(zipStream, replybufferDecompressed);

                    int numBytesFirstHeader = 0;
                    clientMessageToReturn = GetClientMessageFromHeader(unCompressedBytesReceived, ref numBytesFirstHeader);
                    messageParams = Encoding.ASCII.GetString(replybufferDecompressed, numBytesFirstHeader, unCompressedBytesReceived - (numBytesFirstHeader));
                    return clientMessageToReturn;
                }
            }
            catch(SocketException)
            {
                doNotPollServer = true;
                if (System.Windows.Forms.MessageBox.Show("The TCP connection to the server has been lost. Do you wish to save the current game?", "Europe Engulfed .NET", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
                {
                    EuropeEngulfedNETForm.MainForm.SaveGame();
                }
                System.Windows.Forms.Application.Exit();
                return ClientMessageType.OK;
            }
        }

        private void EndSendInformation(IAsyncResult ar)
        {
            ClearReplyInfoBuffer();
            infoSocket.BeginReceive(replyInfobuffer, 0, replyInfobuffer.Length, SocketFlags.None, new AsyncCallback(EndReceiveInformation), null);
        }

        private void QueueMessagesCallback(EuropeEngulfedNETForm form, List<string> messages)
        {
            form.QueueInfoMessages(messages);
        }

        private void EndReceiveInformation(IAsyncResult ar)
        {
            int bytesReceived = infoSocket.EndReceive(ar);
            int bytePosition = 0;
            while (replyInfobuffer[bytePosition] != 0)
            {
                bytePosition++;
                if (bytePosition >= replyInfobuffer.Length)
                    break;
            }

            bytePosition++;
            if (bytePosition < bytesReceived)
            {
                List<string> infoMessages = new List<string>();
                while (bytePosition < bytesReceived)
                {
                    int bytePositionInString = bytePosition;
                    while (replyInfobuffer[bytePositionInString] != 0)
                    {
                        bytePositionInString++;
                        if (bytePositionInString >= replyInfobuffer.Length)
                            break;
                    }
                    string infoMessageString = Encoding.ASCII.GetString(replyInfobuffer, bytePosition, bytePositionInString - bytePosition);
                    if(infoMessageString.Length > 0)
                        infoMessages.Add(infoMessageString);
                    bytePosition += infoMessageString.Length + 1;
                }
                if (infoMessages.Count > 0)
                {
                    if (EuropeEngulfedNETForm.MainForm.InvokeRequired)
                        EuropeEngulfedNETForm.MainForm.Invoke(new EEMainFormQueueMessagesCallback(QueueMessagesCallback), new object[] { EuropeEngulfedNETForm.MainForm, infoMessages });
                    else
                        EuropeEngulfedNETForm.MainForm.QueueInfoMessages(infoMessages);
                }
            }
            waitingToReceiveInfo = false;
        }

        private void EndConnectInformation(IAsyncResult ar)
        {
            waitingToReceiveInfo = false;
        }
    }
}
